/**
 * 
 */
package apibasej.db.transaction;

import java.sql.Blob;
import java.util.ArrayList;
import java.util.Date;

import javax.sql.rowset.serial.SerialBlob;

import apibasej.basic.config.ConfigManager;
import apibasej.db.metadata.Col;
import apibasej.db.metadata.Tab;
import apibasej.db.sql.wrapper.BlockWhere;
import apibasej.db.sql.wrapper.ColSelect;
import apibasej.db.sql.wrapper.Entry;
import apibasej.db.sql.wrapper.Filter;
import apibasej.db.sql.wrapper.FilterValue;
import apibasej.db.sql.wrapper.OrderBy;
import apibasej.db.sql.wrapper.SQLWrapperException;
import apibasej.db.sql.wrapper.SqlPart;
import apibasej.db.sql.wrapper.WrapperSQL;
import apibasej.db.sql.wrapper.WrapperTab;
import apibasej.db.sql.wrapper.WrapperTabJoin;
import apibasej.db.to.ResultSelect;
import apibasej.db.to.StatementParam;
import apibasej.db.to.StatementQuery;
import apibasej.db.to.StatementValues;



/**
 * http://code.google.com/p/apibasej/
 * 
 * @author Julio Azevedo Lessa - apibasej@gmail.com
 * 
 */
public class UtilDB{

	
	// ver ps.getGeneratedKeys() depois de executar o insert para salvar em uma variavel as ultimas PKS inseridas ...
	// chamar dentro de um try catch, e se der erro salvar a excessão em uma var e só lançar quando for ler a pk inserida
	
	// TODO ver group by ... fazer igual o order by ... mas acho que se for um sql mais complexo é melhor setar o sql e os params ...
	// não usar o group by para distinct, usar somente para somas e outros ...
	
	private WrapperSQL wrapperSQL;
	
	//private int queryTimeoutSeconds = 30;// ter como default timeout de 30 segundos
	
	// foi removida esta verificação pois pode ser executado nomamente alterando a paginação, por exemplo ...
	//private boolean alreadyExecuted = false; // (PORQUE???) o UtilDB deve ser usado somente para uma transação
	
	public WrapperSQL getWrapperSQL() {
		return wrapperSQL;
	}
	
	public void setWrapperSQL(WrapperSQL wrapperSQL) {
		this.wrapperSQL = wrapperSQL;
	}
	
	public void setSqlSelect(String sql){
		getWrapperSQL().setFixedSqlSelect(sql);
	}

	public UtilDB(Tab t){
		wrapperSQL = new WrapperSQL( new WrapperTab(t) );
	}
	public UtilDB(WrapperTab t){
		wrapperSQL = new WrapperSQL( t );
	}

	public WrapperTab getTabRoot(){
		return getWrapperSQL().getWrapperTabRoot();
	}
	

	public WrapperTabJoin addTableLeftJoin(Tab t) throws SQLWrapperException{
		return addTableLeftJoin(t, null);
	}
	public WrapperTabJoin addTableLeftJoin(Tab t, WrapperTab tabJoinBefore) throws SQLWrapperException{
		WrapperTabJoin wt = addTableJoin(t,tabJoinBefore);
		wt.setLeftJoin();
		return wt;
	}
	public WrapperTabJoin addTableInnerJoin(Tab t) throws SQLWrapperException{
		return addTableInnerJoin(t, null);
	}
	public WrapperTabJoin addTableInnerJoin(Tab t, WrapperTab tabJoinBefore) throws SQLWrapperException{
		WrapperTabJoin wt = addTableJoin(t,tabJoinBefore);
		wt.setInnerJoin();
		return wt;
	}
	public WrapperTabJoin addTableJoin(Tab t) throws SQLWrapperException{
		return addTableJoin(t, null);
	}
	public WrapperTabJoin addTableJoin(Tab t, WrapperTab tabJoinBefore) throws SQLWrapperException{
		WrapperTabJoin wt = new WrapperTabJoin(t);
		addTableJoin(wt);
		if(tabJoinBefore!=null){
			wt.setTabJoinBefore(tabJoinBefore);
		}
		return wt;
	}
	
	private int ctTab = 0;
	public void addTableJoin(WrapperTabJoin t) throws SQLWrapperException{
		// quando for fazer select com tabs join o alias é obrigatório ... se quiser trocar depois setar por cima do gerado
		if(getWrapperSQL().getWrapperTabValidating().getAliasTab()==null) {
			getWrapperSQL().getWrapperTabValidating().setAliasTab("t"+(ctTab++));
		}
		if(t.getAliasTab()==null) {
			t.setAliasTab("t"+(ctTab++));
		}
		getWrapperSQL().getTabsJoin().add(t);
	}

	
	// ---------------------------------------------------------------------------------

	public BlockWhere getWhere(){
		return getWrapperSQL().getBlockWhere();
	}
	
	// ---------------------------------------------------------------------------------

	
	
	
	public <T> Entry<T> addEntry(Col<T> col, T value) throws SQLWrapperException{// para entries (insert ou update) sempre terá só uma tabela
		Entry<T> e = new Entry<T>(getWrapperSQL().getWrapperTabValidating(), col, value);
		addEntry(e);
		return e;
	}
	
	public Entry<Blob> addEntry(Col<Blob> col, byte[] value) throws UtilDBException, SQLWrapperException{// para entries (insert ou update) sempre terá só uma tabela
		SerialBlob b;
		try {
			b = new SerialBlob(value);
		} catch (Exception e) {
			throw new UtilDBException("Error on create SerialBlob: "+e.getMessage(), e, statementCurrentQuery, statementCurrentUpdate);
			//err("Error on create SerialBlob: "+e.getMessage(),e);
			//return null;// não deve chegar aqui!!!!
		}
		return addEntry(col, b);
	}

	public <T> Entry<T> addEntryConcat(Col<T> col, String valueForConcat) throws SQLWrapperException{
		Entry<T> e = new Entry<T>(getWrapperSQL().getWrapperTabValidating(), col, null);
		e.setValueForConcat(valueForConcat);
		addEntry(e);
		return e;
	}
	public void addEntry(Entry<?> e){
		getWrapperSQL().getEntries().add(e);
	}
	public void addEntryUnchecked(Col<?> col, Object value) throws SQLWrapperException{
		@SuppressWarnings({ "unchecked", "rawtypes" })
		Entry<?> e = new Entry(getWrapperSQL().getWrapperTabValidating(), col, value);
		addEntry(e);
	}
	@SuppressWarnings("unchecked")
	public <D extends Date> Entry<D> addEntryDateNow(Col<D> col) throws SQLWrapperException{
		Date d = ConfigManager.getDateNowSingl();//getDateNow(); // o método getDateNow já configura o timezone e o locale
		if(java.sql.Timestamp.class.equals(col.getClassCol())){
			return addEntry(col, (D)new java.sql.Timestamp(d.getTime()));
		}else if(java.sql.Date.class.equals(col.getClassCol())){
			return addEntry(col, (D)new java.sql.Date(d.getTime()));
		}else if(java.sql.Time.class.equals(col.getClassCol())){
			return addEntry(col, (D)new java.sql.Time(d.getTime()));			
		}else if(java.util.Date.class.equals(col.getClassCol())){
			return addEntry(col, (D)d);
		}
		throw new SQLWrapperException("Invalid type for col: "+col.getClassCol());
	}
	
	// ---------------------------------------------------------------------------------
	public <T> ColSelect<T> addColSelectAsString(String strForColSelectWithoutAlias, String aliasSelect, Class<T> classCol){
		ColSelect<T> c = new ColSelect<T>(strForColSelectWithoutAlias, aliasSelect, classCol);
		addColSelect(c);
		return c;
	}
	public <T> ColSelect<T> addColSelect(Col<T> col, WrapperTab wrapperTabForCol){
		ColSelect<T> c = new ColSelect<T>(wrapperTabForCol, col);
		addColSelect(c);
		return c;
	}
	public <T> ColSelect<T> addColSelect(Col<T> col) throws SQLWrapperException{
		return addColSelect(col, getWrapperSQL().getWrapperTabAddedForCol(col));
	}
	
	
	//este será o count(*)
	public ColSelect<Integer> addColSelectCount() throws SQLWrapperException{
		ColSelect<Integer> c = new ColSelect<Integer>("count(*)", Integer.class);
		c.setReturnPrimitiveIfNull(true);//somente por segurança, pois o count nunca deve retornar null ??????
		addColSelect(c);
		return c;
	}
	public <T> ColSelect<T> addColSelectCount(Col<T> col) throws SQLWrapperException{
		ColSelect<T> cs = addColSelect(col);
		cs.setStrForColSelectWithoutAlias("COUNT("+cs.getStrForColSelectWithoutAlias()+")");
		return cs;
	}
	public <T> ColSelect<T> addColSelectSum(Col<T> col) throws SQLWrapperException{
		ColSelect<T> cs = addColSelect(col);
		cs.setStrForColSelectWithoutAlias("SUM("+cs.getStrForColSelectWithoutAlias()+")");
		return cs;
	}
	public <T> ColSelect<T> addColSelectMax(Col<T> col) throws SQLWrapperException{
		ColSelect<T> cs = addColSelect(col);
		cs.setStrForColSelectWithoutAlias("MAX("+cs.getStrForColSelectWithoutAlias()+")");
		return cs;
	}
	public <T> ColSelect<T> addColSelectMin(Col<T> col) throws SQLWrapperException{
		ColSelect<T> cs = addColSelect(col);
		cs.setStrForColSelectWithoutAlias("MIN("+cs.getStrForColSelectWithoutAlias()+")");
		return cs;
	}
	public void addColSelect(ColSelect<?> colSelect){
		getWrapperSQL().getColsSelect().add(colSelect);
	}
	public void addAllColsForSelect() throws SQLWrapperException{
		addAllColsForSelect(false);
	}
	public void addAllColsForSelect(boolean returnPrimitivesForNulls) throws SQLWrapperException{
		for(Col<?> c : getWrapperSQL().getWrapperTabRoot().getTab().getCols().values()){
			ColSelect<?> cs = addColSelect(c);
			if(returnPrimitivesForNulls) cs.setReturnPrimitiveIfNull(returnPrimitivesForNulls);
		}
		for(WrapperTabJoin tj : getWrapperSQL().getTabsJoin()){
			for(Col<?> c : tj.getTab().getCols().values()){
				ColSelect<?> cs = addColSelect(c);
				if(returnPrimitivesForNulls) cs.setReturnPrimitiveIfNull(returnPrimitivesForNulls);
			}
		}
	}
	// ---------------------------------------------------------------------------------
	
	// addColOrderBy(Col c) que varre as colsSelect e busca uma col igual e esta
	// evitar usar este método ... usar o addColOrderBy(ColSelect<T> colOrderBy)
	// só funciona para colunas da tabela root, se quiser ordernar por uma coluna de de uma tabJoin usar addColOrderBy(ColSelect<T> colOrderBy)
	@SuppressWarnings("unchecked")
	public <T> OrderBy<T> addColOrderBy(Col<T> colOrderBy, WrapperTab wrapperTabForCol) throws SQLWrapperException{
		ColSelect<T> o = null;
		for(ColSelect<?> cs : getWrapperSQL().getColsSelect()){
			if(cs.getCol()==colOrderBy && cs.getWrapperTab()==wrapperTabForCol) {
				o = (ColSelect<T>)cs;
				break;
			}
		}
		if(o==null) return addColOrderBy( new ColSelect<T>(wrapperTabForCol,colOrderBy) );// se não achar nas colunas de resposta cria uma COlSelect com a tab root
		else return addColOrderBy(o);
	}
	public <T> OrderBy<T> addColOrderBy(Col<T> colOrderBy) throws SQLWrapperException{
		return addColOrderBy(colOrderBy, getWrapperSQL().getWrapperTabAddedForCol(colOrderBy));
	}
	public <T> OrderBy<T> addColOrderBy(ColSelect<T> colOrderBy){
		OrderBy<T> wrapperOrderBy = new OrderBy<T>(colOrderBy);
		addColOrderBy(wrapperOrderBy);
		return wrapperOrderBy;
	}
	public void addColOrderBy(OrderBy<?> wrapperOrderBy){ // o padrão é asc
		getWrapperSQL().getColsOrderBy().add(wrapperOrderBy);
	}
	
	public void addColGroupBy(ColSelect<?> col) throws SQLWrapperException{
		getWrapperSQL().getColsGroupBy().add(col);
	}
	// ---------------------------------------------------------------------------------
	
	
	
	//-----------------------------------------------
	
	public StatementValues fillValuesInsert() throws SQLWrapperException, UtilDBException{
		String sql = getWrapperSQL().generateSqlInsert();
		fillStatementValues(sql, getWrapperSQL(), getCurrentStatementUpdate());
		return getCurrentStatementUpdate();
	}
	public StatementValues fillValuesUpdate() throws SQLWrapperException, UtilDBException{
		String sql = getWrapperSQL().generateSqlUpdate();
		fillStatementValues(sql, getWrapperSQL(), getCurrentStatementUpdate());
		return getCurrentStatementUpdate();
	}
	public StatementValues fillValuesDelete() throws SQLWrapperException, UtilDBException{
		String sql = getWrapperSQL().generateSqlDelete();
		fillStatementValues(sql, getWrapperSQL(), getCurrentStatementUpdate());
		return getCurrentStatementUpdate();
	}
	public StatementQuery fillValuesQuery() throws SQLWrapperException, UtilDBException{
		String sql = getWrapperSQL().getSqlSelect().toString();
		fillStatementValues(sql, getWrapperSQL(), getCurrentStatementQuery());
		return getCurrentStatementQuery();
	}
	
	//************** EXECUÇÕES *****************
	public int execInsert(GenericTransaction c) throws UtilDBException, SQLWrapperException{
		StatementValues sv = fillValuesInsert();
		//getConfig().fireTriggers(c,this,sv); // verificar os parâmetros, se for UtilConnection no server registrar a data de alteração das tabelas para cada organizacao
		return UtilExecStatement.execStatementUpdate(c,sv);
	}
	public int execUpdate(GenericTransaction c) throws SQLWrapperException, UtilDBException{
		StatementValues sv = fillValuesUpdate();
		//getConfig().fireTriggers(c,this,sv); // verificar os parâmetros, se for UtilConnection no server registrar a data de alteração das tabelas para cada organizacao
		return UtilExecStatement.execStatementUpdate(c,sv);
	}
	public int execDelete(GenericTransaction c) throws SQLWrapperException, UtilDBException{
		StatementValues sv = fillValuesDelete();
		//getConfig().fireTriggers(c,this,sv); // verificar os parâmetros, se for UtilConnection no server registrar a data de alteração das tabelas para cada organizacao
		return UtilExecStatement.execStatementUpdate(c,sv);
	}
	public CursorSelect createCursorSelect(GenericTransaction tr) throws SQLWrapperException, UtilDBException{
		return tr.executeQuery(fillValuesQuery());
	}
	public ResultSelect execQuery(GenericTransaction c) throws SQLWrapperException, UtilDBException{
		return UtilExecStatement.execStatementQuery(c, fillValuesQuery());
	}
	
	public Row getNextResultRow(CursorSelect cs) throws SQLWrapperException, UtilDBException{
		//verifyIfAlreadyExecuted();
		try {
			if(cs.next()){
				Row row = new Row();
				for(int i=0;i<cs.getCtCols();i++) {
					if(getWrapperSQL().getColsSelect().size()>0){// se tem colunas de select
						ColSelect<?> c = getWrapperSQL().getColsSelect().get(i);
						row.add(c, cs.getValResultSet(c.getClassCol(), c.isIgnoreResultNullForPrimitives(), i));
					}else{// se não tem ... for select * ...
						
						//TODO ... tratar para não precisar do isUseAsterisk() ??? e usar * quando não tiver colunas ????
						
						if(getWrapperSQL().isUseAsterisk()){
							String nameCol = cs.getNameColsStr().get(i);
							ColSelect<?> colSel = new ColSelect<>(nameCol, null);
							row.add(colSel, cs.getValResultSet(null, false, i));
						}else{
							//registrar logs de alerta para forçar adicionar as colunas !!!
							throw new SQLWrapperException("Nenhuma coluna foi adicionada para o select!");
						}
					}
				}
				return row;
			}
			cs.verifyCloseConnectionAfterExecute();
			return null; // Quando retornar null já encerrou o select. Só precisará chamar o endSelect() se não usar o while( (r=dbAux.getNextResultRow())!=null ) ...
		} catch (Throwable e) {
			cs.close();
			throw new UtilDBException("Error on read SELECT", e, statementCurrentQuery, statementCurrentUpdate);
		}
		// não usar finaly pois esta unção pode ser chamada várias vezes ...
	}
	
	public ArrayList<Row> execSelect(GenericTransaction c) throws UtilDBException{
		try{
			ArrayList<Row> dadosResp = new ArrayList<Row>();
			Row row;
			CursorSelect cs = createCursorSelect(c);
			while( (row=getNextResultRow(cs)) != null ){
				dadosResp.add(row);
			}
			cs.verifyCloseConnectionAfterExecute();// somente por segurança, pois o getNextResultRow(cs) deve fechar depois de varrer todos
			return dadosResp;
		}catch (UtilDBException e) {
			if(c!=null && c.isAutomaticClose()){//só fecha se for AutomaticClose, pois pode ser uma exceção tratada e depois continuar a executar ...?
				c.close();
			}
			throw e;
		}catch (Throwable e) {
			if(c!=null && c.isAutomaticClose()){//só fecha se for AutomaticClose, pois pode ser uma exceção tratada e depois continuar a executar ...?
				c.close();
			}
			throw new UtilDBException("Error on read response of select", e, statementCurrentQuery, statementCurrentUpdate);
		}
	}
	
	public Row execSelectUniqueRow(GenericTransaction c) throws UtilDBException{
		ArrayList<Row> lins = execSelect(c);
		if(lins.size()!=1) {
			String m = "Esta consulta deveria retornar uma linha. Linhas retornadas: "+lins.size();
			throw new UtilDBException(m, statementCurrentQuery, statementCurrentUpdate);
		}
		return lins.get(0);
	}
	
	public <T> ArrayList<T> execSelectUniqueCol(Class<T> typeCol, GenericTransaction c) throws SQLWrapperException, UtilDBException{
		//setar somente uma ColSelect 
		if(getWrapperSQL().getColsSelect().size()>1) {
			String m = "Mais de uma coluna foi recebida para execSelectUniqueCol(...)";
			throw new UtilDBException(m, statementCurrentQuery, statementCurrentUpdate);
		}
		ArrayList<Row> lins = execSelect(c);
		ArrayList<T> resp = new ArrayList<T>();
		for(Row lin : lins){
			resp.add( typeCol.cast(lin.get(0)) );
		}
		return resp;
	}
	public <T> T execSelectUniqueValue(Class<T> typeCol, GenericTransaction c) throws SQLWrapperException, UtilDBException{
		return typeCol.cast(execSelectUniqueValue(c));
	}
	public Object execSelectUniqueValue(GenericTransaction c) throws SQLWrapperException, UtilDBException{
		Row lin = execSelectUniqueRow(c);
		if(lin.numCols()!=1) {
			String m = "Esta consulta deveria retornar apenas uma coluna. Colunas retornadas: "+lin.numCols();
			throw new UtilDBException(m, statementCurrentQuery, statementCurrentUpdate);
		}
		return lin.get(0);
	}
	//***************************************
	
	
	private StatementValues statementCurrentUpdate;
	public StatementValues getCurrentStatementUpdate() {
		if(statementCurrentUpdate==null) statementCurrentUpdate = new StatementValues();
		return statementCurrentUpdate;
	}
	private StatementQuery statementCurrentQuery;
	public StatementQuery getCurrentStatementQuery() {
		if(statementCurrentQuery==null) statementCurrentQuery = new StatementQuery();
		return statementCurrentQuery;
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static final void fillStatementValues(String sql, WrapperSQL ws, StatementValues sv) throws SQLWrapperException, UtilDBException{ //, Connection c
		//conf.getProp(CommonProperties.LOGGER).finest("SQL: "+sql);
		sv.setSql(sql);
		sv.getParams().clear();// somente por garantia ...
		if(ws.getColsSelect().size()>0){
			ArrayList<Class<?>> typesCols = new ArrayList<Class<?>>();
			for(ColSelect<?> c : ws.getColsSelect()){
				typesCols.add(c.getClassCol());
			}
			sv.setTypesColsSelect(typesCols);
		}
		for(Entry<?> in : ws.getEntries()){// no caso de select deve ser vazio, no caso de update os entries sempre são primeiros
			//conf.getProp(CommonProperties.LOGGER).finest("PARAM_IN="+in.getValue()+" (COL: "+(in.getCol()==null?"null":in.getCol().getName())+")"); 
			
			//o param deve ser setado mesmo se tiver valueForConcat, pois o valueForConcat pode ter o param: ?
			//if(in.getValueForConcat()==null){ //comentado em 02/06/2014
				// precisa informar o tipo para alguns casos, como para inserir Long no access
				// no UtilDBStr não tem o Type da col, por isso não deve tentar inserir valor null
				/*
				if(in.getValue()==null) ps.setNull(paramIndex++,in.getCol().getType());
				else if(in.getCol().getType()!=null && in.getCol().getType()!=0) ps.setObject(paramIndex++,in.getValue(),in.getCol().getType());
				else ps.setObject(paramIndex++,in.getValue());
				*/
				StatementParam sp = new StatementParam(); 
				sp.setValue(in.getValue());
				if(in.getCol()!=null) {
					sp.setType(in.getCol().getType());
				}
				if(in.isGenIdAfter()){
					sp.setNameColWithTabToGenId(in.getCol().getNameWithTab());
				}
				sv.getParams().add(sp);
			//}
		}
		for(SqlPart<?> w : ws.getBlockWhere().getAllChildFilters()){// no caso de insert deve ser vazio    //ws.getPartsWhere()
			if(w.isFilter()){
				Filter<?> f = (Filter<?>)w;
				boolean ifSetParamInQuery = f.ifSetParamInQuery();
				//conf.getProp(CommonProperties.LOGGER).finest("PARAM_FILTER="+f.getValue()+" (COL: "+(f.getCol()==null?"null":f.getCol().getName())+") ifSetParamInQuery="+ifSetParamInQuery); 
				if(ifSetParamInQuery) {
					/*
					if(f.getCol()!=null && f.getCol().getType()!=null && f.getCol().getType()!=0) ps.setObject(paramIndex++,f.getValue(),f.getCol().getType());
					else ps.setObject(paramIndex++,f.getValue());
					*/
					if(f.getValues()!=null && f.getValues().size()>0){
						if(f.getValue()!=null){
							throw new UtilDBException("Unique value must be null for filter with many values: \""+f.getStrForWhere()+"\"", sv, null);
						}
						for(FilterValue fv : f.getValues()){
							if(fv==null || fv.getValue()==null){
								throw new UtilDBException("Filter value cannot be null: \""+f.getStrForWhere()+"\"", sv, null);
							}
							StatementParam sp = new StatementParam(); 
							sp.setValue(fv.getValue());
							sp.setType(fv.getType());
							sv.getParams().add(sp);
						}

					}else if(f.getValue()!=null){
						StatementParam sp = new StatementParam(); 
						sp.setValue(f.getValue());
						if(f.getCol()!=null) {
							sp.setType(f.getCol().getType());
						}
						sv.getParams().add(sp);
					}else{
						throw new UtilDBException("Filter value cannot be null: \""+f.getStrForWhere()+"\"", sv, null);
					}
					
				}
				//}else if(w.isStrConcatWhere()){
				//conf.getProp(CommonProperties.LOGGER).finest("PARAM_CONCAT="+((StrConcatWhere)w).getStrConcat()); 
			
			}else{// não deve ocorrer
				String m = "Invalid class wrapper for where: "+w.getClass().getName();
				throw new UtilDBException(m, sv, null);
			}
		}
		//return sv;//ps;
	}
	
	public boolean isPermitDeleteUpdateWithoutFiltrers() {
		return getWrapperSQL().isPermitDeleteUpdateWithoutFiltrers();
	}
	public void setPermitDeleteUpdateWithoutFiltrers(boolean permitDeleteUpdateWithoutFiltrers) {
		getWrapperSQL().setPermitDeleteUpdateWithoutFiltrers(permitDeleteUpdateWithoutFiltrers);
	}

	
	//Retorna a última PK inserida no banco na dada tabela.
	public Object getLastPrimaryKey() throws SQLWrapperException{
		return null; // ver como recuperar, pode ser sobrescrito
	}
	
	public void setUseAsterisk(boolean useAsterisk) {
		getWrapperSQL().setUseAsterisk(useAsterisk);
	}
	public boolean isUseAsterisk() {
		return getWrapperSQL().isUseAsterisk();
	}
	
	public boolean isSelectDistinct() {
		return getWrapperSQL().isSelectDistinct();
	}
	public void setSelectDistinct(boolean isSelectDistinct) {
		getWrapperSQL().setSelectDistinct(isSelectDistinct);
	}
//	public void setQueryTimeoutSeconds(int queryTimeoutSeconds) {
//		this.queryTimeoutSeconds = queryTimeoutSeconds;
//	}
//	public int getQueryTimeoutSeconds() {
//		return queryTimeoutSeconds;
//	}
	
	
}
